#include "../../Include/Utils/WizUtilsMD5.hpp"
#include "../../Include/Base/Utils/WizBaseUtilsMemory.hpp"

/* 
* RFC 1321 compliant MD5 implementation, 
* by Christophe Devine <devine@cr0.net>;
* this program is licensed under the GPL. 
*/ 
#include <stdio.h> 
#include <stdlib.h> 

namespace Wiz
{
    namespace MD5
    {
        namespace Context
        {
            struct WIZ_PRIVATE Type 
            { 
                ::Wiz::UInt32::Type m_Total[2];
                ::Wiz::UInt32::Type m_State[4];
                ::Wiz::UInt8::Type  m_Buffer[64];
            };
            WIZ_DECLARE(::Wiz::MD5::Context::Type);
        } /// end of namespace Context

#define GET_UInt32(n,b,i) \
        { \
        (n) = (::Wiz::UInt32::Type) ((::Wiz::UInt8::Type *) b)[(i)] \
        | (((::Wiz::UInt32::Type) ((::Wiz::UInt8::Type *) b)[(i)+1]) << 8) \
        | (((::Wiz::UInt32::Type) ((::Wiz::UInt8::Type *) b)[(i)+2]) << 16) \
        | (((::Wiz::UInt32::Type) ((::Wiz::UInt8::Type *) b)[(i)+3]) << 24);\
    } 

#define PUT_UInt32(n,b,i) \
        { \
        (((::Wiz::UInt8::Type *) b)[(i)] ) = (::Wiz::UInt8::Type) (((n) ) & 0xFF);\
        (((::Wiz::UInt8::Type *) b)[(i)+1]) = (::Wiz::UInt8::Type) (((n) >> 8) & 0xFF);\
        (((::Wiz::UInt8::Type *) b)[(i)+2]) = (::Wiz::UInt8::Type) (((n) >> 16) & 0xFF);\
        (((::Wiz::UInt8::Type *) b)[(i)+3]) = (::Wiz::UInt8::Type) (((n) >> 24) & 0xFF);\
    }

        WIZ_PRIVATE ::Wiz::Void::Type Begin(::Wiz::MD5::Context::Ptr CtxPtr) 
        { 
           CtxPtr->m_Total[0] = 0;
           CtxPtr->m_Total[1] = 0;
           CtxPtr->m_State[0] = 0x67452301;
           CtxPtr->m_State[1] = 0xEFCDAB89;
           CtxPtr->m_State[2] = 0x98BADCFE;
           CtxPtr->m_State[3] = 0x10325476;
        } 

        WIZ_PRIVATE ::Wiz::Void::Type Process(::Wiz::MD5::Context::Ptr CtxPtr, ::Wiz::UInt8::Type data[64]) 
        { 
            ::Wiz::UInt32::Type A, B, C, D, X[16];

            GET_UInt32( X[0], data, 0);
            GET_UInt32( X[1], data, 4);
            GET_UInt32( X[2], data, 8);
            GET_UInt32( X[3], data, 12);
            GET_UInt32( X[4], data, 16);
            GET_UInt32( X[5], data, 20);
            GET_UInt32( X[6], data, 24);
            GET_UInt32( X[7], data, 28);
            GET_UInt32( X[8], data, 32);
            GET_UInt32( X[9], data, 36);
            GET_UInt32( X[10], data, 40);
            GET_UInt32( X[11], data, 44);
            GET_UInt32( X[12], data, 48);
            GET_UInt32( X[13], data, 52);
            GET_UInt32( X[14], data, 56);
            GET_UInt32( X[15], data, 60);

#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))

#define P(a,b,c,d,k,s,t) \
            { \
            a += F(b,c,d) + X[k] + t;a = S(a,s) + b;\
            } 

            A = CtxPtr->m_State[0];
            B = CtxPtr->m_State[1];
            C = CtxPtr->m_State[2];
            D = CtxPtr->m_State[3];

#define F(x,y,z) (z ^ (x & (y ^ z))) 

            P(A, B, C, D, 0, 7, 0xD76AA478);
            P(D, A, B, C, 1, 12, 0xE8C7B756);
            P(C, D, A, B, 2, 17, 0x242070DB);
            P(B, C, D, A, 3, 22, 0xC1BDCEEE);
            P(A, B, C, D, 4, 7, 0xF57C0FAF);
            P(D, A, B, C, 5, 12, 0x4787C62A);
            P(C, D, A, B, 6, 17, 0xA8304613);
            P(B, C, D, A, 7, 22, 0xFD469501);
            P(A, B, C, D, 8, 7, 0x698098D8);
            P(D, A, B, C, 9, 12, 0x8B44F7AF);
            P(C, D, A, B, 10, 17, 0xFFFF5BB1);
            P(B, C, D, A, 11, 22, 0x895CD7BE);
            P(A, B, C, D, 12, 7, 0x6B901122);
            P(D, A, B, C, 13, 12, 0xFD987193);
            P(C, D, A, B, 14, 17, 0xA679438E);
            P(B, C, D, A, 15, 22, 0x49B40821);

#undef F 

#define F(x,y,z) (y ^ (z & (x ^ y))) 

            P(A, B, C, D, 1, 5, 0xF61E2562);
            P(D, A, B, C, 6, 9, 0xC040B340);
            P(C, D, A, B, 11, 14, 0x265E5A51);
            P(B, C, D, A, 0, 20, 0xE9B6C7AA);
            P(A, B, C, D, 5, 5, 0xD62F105D);
            P(D, A, B, C, 10, 9, 0x02441453);
            P(C, D, A, B, 15, 14, 0xD8A1E681);
            P(B, C, D, A, 4, 20, 0xE7D3FBC8);
            P(A, B, C, D, 9, 5, 0x21E1CDE6);
            P(D, A, B, C, 14, 9, 0xC33707D6);
            P(C, D, A, B, 3, 14, 0xF4D50D87);
            P(B, C, D, A, 8, 20, 0x455A14ED);
            P(A, B, C, D, 13, 5, 0xA9E3E905);
            P(D, A, B, C, 2, 9, 0xFCEFA3F8);
            P(C, D, A, B, 7, 14, 0x676F02D9);
            P(B, C, D, A, 12, 20, 0x8D2A4C8A);

#undef F 

#define F(x,y,z) (x ^ y ^ z) 

            P(A, B, C, D, 5, 4, 0xFFFA3942);
            P(D, A, B, C, 8, 11, 0x8771F681);
            P(C, D, A, B, 11, 16, 0x6D9D6122);
            P(B, C, D, A, 14, 23, 0xFDE5380C);
            P(A, B, C, D, 1, 4, 0xA4BEEA44);
            P(D, A, B, C, 4, 11, 0x4BDECFA9);
            P(C, D, A, B, 7, 16, 0xF6BB4B60);
            P(B, C, D, A, 10, 23, 0xBEBFBC70);
            P(A, B, C, D, 13, 4, 0x289B7EC6);
            P(D, A, B, C, 0, 11, 0xEAA127FA);
            P(C, D, A, B, 3, 16, 0xD4EF3085);
            P(B, C, D, A, 6, 23, 0x04881D05);
            P(A, B, C, D, 9, 4, 0xD9D4D039);
            P(D, A, B, C, 12, 11, 0xE6DB99E5);
            P(C, D, A, B, 15, 16, 0x1FA27CF8);
            P(B, C, D, A, 2, 23, 0xC4AC5665);

#undef F 

#define F(x,y,z) (y ^ (x | ~z)) 

            P(A, B, C, D, 0, 6, 0xF4292244);
            P(D, A, B, C, 7, 10, 0x432AFF97);
            P(C, D, A, B, 14, 15, 0xAB9423A7);
            P(B, C, D, A, 5, 21, 0xFC93A039);
            P(A, B, C, D, 12, 6, 0x655B59C3);
            P(D, A, B, C, 3, 10, 0x8F0CCC92);
            P(C, D, A, B, 10, 15, 0xFFEFF47D);
            P(B, C, D, A, 1, 21, 0x85845DD1);
            P(A, B, C, D, 8, 6, 0x6FA87E4F);
            P(D, A, B, C, 15, 10, 0xFE2CE6E0);
            P(C, D, A, B, 6, 15, 0xA3014314);
            P(B, C, D, A, 13, 21, 0x4E0811A1);
            P(A, B, C, D, 4, 6, 0xF7537E82);
            P(D, A, B, C, 11, 10, 0xBD3AF235);
            P(C, D, A, B, 2, 15, 0x2AD7D2BB);
            P(B, C, D, A, 9, 21, 0xEB86D391);

#undef F 
           CtxPtr->m_State[0] += A;
           CtxPtr->m_State[1] += B;
           CtxPtr->m_State[2] += C;
           CtxPtr->m_State[3] += D;
        } 

        WIZ_PRIVATE ::Wiz::Void::Type Update(::Wiz::MD5::Context::Ptr CtxPtr, ::Wiz::UInt8::Ptr input, ::Wiz::Size::Type DataSZ ) 
        { 
            ::Wiz::UInt32::Type left, fill;

            if (DataSZ == 0)
            {
                return;
            }

            left = (CtxPtr->m_Total[0] >> 3 ) & 0x3F;
            fill = 64 - left;

           CtxPtr->m_Total[0] += DataSZ << 3;
           CtxPtr->m_Total[1] += DataSZ >> 29;

           CtxPtr->m_Total[0] &= 0xFFFFFFFF;
           CtxPtr->m_Total[1] +=CtxPtr->m_Total[0] < DataSZ << 3;

            if( left && DataSZ >= fill ) 
            { 
                memcpy( (::Wiz::Void::Ptr) (CtxPtr->m_Buffer + left), (::Wiz::Void::Ptr) input, fill);
                Process( CtxPtr,CtxPtr->m_Buffer);
                DataSZ -= fill;
                input += fill;
                left = 0;
            } 

            while( DataSZ >= 64 ) 
            { 
                Process( CtxPtr, input);
                DataSZ -= 64;
                input += 64;
            } 

            if( DataSZ ) 
            { 
                memcpy( (::Wiz::Void::Ptr) (CtxPtr->m_Buffer + left), (::Wiz::Void::Ptr) input, DataSZ);
            } 
        } 

        WIZ_PRIVATE ::Wiz::Void::Type End(::Wiz::MD5::Context::Ptr CtxPtr, ::Wiz::UInt8::Type digest[16] ) 
        { 
            ::Wiz::UInt8::Type md5_padding[64] = 
            { 
                0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 
            };

            ::Wiz::UInt32::Type last, padn;
            ::Wiz::UInt8::Type msglen[8];

            PUT_UInt32(CtxPtr->m_Total[0], msglen, 0);
            PUT_UInt32(CtxPtr->m_Total[1], msglen, 4);

            last = (CtxPtr->m_Total[0] >> 3 ) & 0x3F;
            padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last);

            Update(CtxPtr, md5_padding, padn);
            Update(CtxPtr, msglen, 8);

            PUT_UInt32(CtxPtr->m_State[0], digest, 0);
            PUT_UInt32(CtxPtr->m_State[1], digest, 4);
            PUT_UInt32(CtxPtr->m_State[2], digest, 8);
            PUT_UInt32(CtxPtr->m_State[3], digest, 12);
        } 

        ::Wiz::Void::Type Generate(::Wiz::UInt8::Type OutMD5[16], ::Wiz::Byte::Ptr DataPtr, ::Wiz::Size::In DataSZ)
        {
            ::Wiz::MD5::Context::Type Cntxt;
            Begin(&Cntxt);
            Update(&Cntxt, DataPtr, DataSZ);
            End(&Cntxt, OutMD5);
        }

        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////

        Type::Type() 
        { 
            ::Wiz::Memory::Zero(m_Data);
        }
        Type::Type(::Wiz::UInt8::Ptr MD5Src) 
        { 
            ::Wiz::Memory::Copy(m_Data, MD5Src);
        } 
        ::Wiz::Int::Type _httoi(::Wiz::Char::In InChar1, ::Wiz::Char::In InChar2) 
        { 
            struct CHexMap 
            { 
                ::Wiz::Char::Type m_Char;
                ::Wiz::Int::Type  m_Value;
            };

            ::Wiz::Int::TypeConst HexMapL = 16;
            static CHexMap HexMap[HexMapL] = 
            { 
                {'0', 0}, {'1', 1}, 
                {'2', 2}, {'3', 3}, 
                {'4', 4}, {'5', 5}, 
                {'6', 6}, {'7', 7}, 
                {'8', 8}, {'9', 9}, 
                {'a', 10}, {'b', 11}, 
                {'c', 12}, {'d', 13}, 
                {'e', 14}, {'f', 15}
            };
            ::Wiz::Int::Type result  = 0;
            ::Wiz::Int::Type i = 0;
            for (;i < HexMapL;i++) 
            { 
                if (InChar1 == HexMap[i].m_Char)
                {
                    result |= HexMap[i].m_Value;
                    break;
                }
            }
            result <<= 4;
            for (i = 0; i < HexMapL; i++) 
            { 
                if (InChar2 == HexMap[i].m_Char)
                {
                    result |= HexMap[i].m_Value;
                    break;
                } 
            } 

            return result;
        } 

        Type::Type(::Wiz::Char::ConstPtr MD5Src) 
        { 
            if (::Wiz::StrCmp(MD5Src,"") == 0) 
            { 
                ::Wiz::Memory::Zero(m_Data);
                return;
            }

            if (MD5Src[0] == '0' && (MD5Src[1] == 'X' || MD5Src[1] == 'x'))
            {
                MD5Src += 2;
            }

            for( ::Wiz::Int::Type j = 0;j < 16;j++, MD5Src += 2 ) 
            {
                m_Data[j] = ::Wiz::MD5::_httoi(MD5Src[0], MD5Src[1]);
            } 
        } 

        Type Type::operator +(tThisIn adder) 
        { 
            ::Wiz::UInt8::Type NewData[16];
            for (::Wiz::Int::Type i = 0; i < 16; i++)
            {
                NewData[i] = m_Data[i] ^ (adder.m_Data[i]);
            }
            return Type(NewData);
        } 

        ::Wiz::Bool::Type Type::operator ==(tThisIn Cmper) 
        { 
            return ::memcmp(Cmper.m_Data, m_Data, 16) == 0;
        } 

        ::Wiz::String::Type Type::ToString() 
        { 
            ::Wiz::Char::Type output[33];
            ::Wiz::UInt::Type j = 0;
            for(; j < 16; j++ ) 
            { 
                sprintf( output + j * 2, "%02x", m_Data[j]);
            } 
            return ::Wiz::String::Type(output);
        }
        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////
    } /// end of namespace MD5
} /// end of namespace Wiz
